﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Diagnostics;
using System.ComponentModel;

namespace Antlers
{
	internal class ThumbnailViewPanel : Control
	{
		private List<Thumbnail> m_thumbnails = new List<Thumbnail>();
		private LinkedList<Thumbnail> m_selectedThumbnails = new LinkedList<Thumbnail>();
		private bool m_isDirty = false;
		private int m_columnCount = 0;
		private int m_rowCount = 0;
		private int m_startRow = 0;
		private int m_visibleRowCount = 0;
		private int m_shiftIndex = -1;

		private PaintBox m_canvas;
		private ScrollBar m_scroll;
		private ContextMenu m_contextMenu;

		private int m_thumbSize = 120;
		private int m_scrollIncrement = 20;
		private int m_bigScrollIncrement = 100;

		public ThumbnailViewPanel()
		{
			Resize += new EventHandler(Handler_Resize);

			m_canvas = new PaintBox();
			m_canvas.Margin = new Padding(0);
			m_canvas.MouseClick += new MouseEventHandler(Handler_MouseClick);
			m_canvas.MouseDoubleClick += new MouseEventHandler(Handler_MouseDoubleClick);
			m_canvas.MouseWheel += new MouseEventHandler(Handler_MouseWheel);
			Controls.Add(m_canvas);

			m_scroll = new VScrollBar();
			m_scroll.Dock = DockStyle.Right;
			m_scroll.Visible = false;
			m_scroll.SmallChange = m_scrollIncrement;
			m_scroll.LargeChange = m_bigScrollIncrement;
			m_scroll.Scroll += new ScrollEventHandler(Handler_Scroll);
			m_scroll.ValueChanged += new EventHandler(Handler_ValueChanged);
			Controls.Add(m_scroll);

			m_contextMenu = new ContextMenu();
		}

		public delegate void SelectionChangedEventHandler(IEnumerable<Thumbnail> selection);
		public event SelectionChangedEventHandler SelectionChanged;

		public delegate void ForceUpdateCallback();

		internal void SetThumbnailList(IEnumerable<Thumbnail> thumbnails)
		{
			m_thumbnails = new List<Thumbnail>(thumbnails);
			SetDirty();
			ForceUpdate();
		}

		protected override void OnPaint(PaintEventArgs e)
		{
			CleanUp();
			base.OnPaint(e);
		}

		private void SetDirty()
		{
			m_isDirty = true;
		}

		private void CleanUp()
		{
			if (m_isDirty)
			{
				// update the total column and row counts
				m_columnCount = Width / m_thumbSize;
				m_rowCount = (int)Math.Ceiling((double)m_thumbnails.Count / (double)m_columnCount);

				// the content doesn't fit, so recalc and show the scroll bar
				if (m_rowCount * m_thumbSize > Height)
				{
					// recaclc the dimensions to account for the scroll bar
					// REFACTOR: duplicate code
					m_columnCount = (Width - SystemInformation.VerticalScrollBarWidth) / m_thumbSize;
					m_rowCount = (int)Math.Ceiling((double)m_thumbnails.Count / (double)m_columnCount);

					// set and display the scroll bar
					m_scroll.Maximum = ((m_rowCount - m_visibleRowCount + 1) * m_scrollIncrement) + m_bigScrollIncrement - 1;
					m_scroll.Visible = true;
				}
				else
				{
					// everything fits in the current view, so hide the scroll bar
					m_scroll.Visible = false;
				}

				// update the canvas dimensions. be careful not to hide the scroll bar!
				m_canvas.Height = m_rowCount * m_thumbSize;
				m_canvas.Width = m_columnCount * m_thumbSize;

				// determine the number of visible row. we'll use this to limit the about we need to render
				m_visibleRowCount = (int)Math.Ceiling((double)Height / (double)m_thumbSize);

				m_isDirty = false;
			}
		}

		private void ForceUpdate()
		{
			if (InvokeRequired)
			{
				Invoke(new ForceUpdateCallback(ForceUpdate));
			}
			else
			{
				Invalidate();
				m_canvas.Invalidate();
				Update();
			}
		}

		private void UpdateScrollStartRow()
		{
			m_startRow = m_scroll.Value / m_scrollIncrement;
			ForceUpdate();
		}

		private void BuildThumbnailContextMenu()
		{
			m_contextMenu.MenuItems.Clear();

			MenuItem menu_open = new MenuItem("Open", Handler_MenuItem_Open);
			m_contextMenu.MenuItems.Add(menu_open);
		}

		private void Handler_Scroll(object sender, ScrollEventArgs sea)
		{
			UpdateScrollStartRow();
		}

		private void Handler_ValueChanged(object sender, EventArgs args)
		{
			UpdateScrollStartRow();
		}

		private void Handler_Resize(object sender, EventArgs args)
		{
			SetDirty();
			ForceUpdate();
		}

		public void Handler_MouseWheel(object sender, MouseEventArgs args)
		{
			m_scroll.Value = Math.Min(m_scroll.Maximum - m_bigScrollIncrement + 1, Math.Max(m_scroll.Minimum, m_scroll.Value - (m_scrollIncrement * Math.Sign(args.Delta))));
		}

		// Modify the current selection and force a UI update
		private void Handler_MouseClick(object sender, MouseEventArgs args)
		{
			m_canvas.Focus();

			int row = args.Y / m_thumbSize;
			int col = args.X / m_thumbSize;

			row += m_startRow;
			int index = (row * m_columnCount) + col;
			if (index < m_thumbnails.Count)
			{
				Thumbnail selected = m_thumbnails[index];
				bool selectionChanged = false;

				if (args.Button == MouseButtons.Left)
				{// if neither shift nor control is pressed
					if (((ModifierKeys & (Keys.Shift | Keys.Control)) == 0))
					{
						m_selectedThumbnails.Clear();
						selectionChanged = true;
					}

					// if shift is pressed
					if ((ModifierKeys & Keys.Shift) == Keys.Shift)
					{
						if (m_shiftIndex != -1)
						{
							m_selectedThumbnails.Clear();
							int lower = Math.Min(m_shiftIndex, index);
							int higher = Math.Max(m_shiftIndex, index);
							for (int i = lower; i <= higher; i++)
							{
								m_selectedThumbnails.AddLast(m_thumbnails[i]);
							}

							selectionChanged = true;
						}
					}
					else
					{
						m_shiftIndex = -1;
					}

					if (!m_selectedThumbnails.Contains(selected))
					{
						m_selectedThumbnails.AddLast(selected);
						selectionChanged = true;
					}
					else if ((ModifierKeys & Keys.Control) == Keys.Control)
					{
						m_selectedThumbnails.Remove(selected);
						selectionChanged = true;
						m_shiftIndex = m_thumbnails.IndexOf(selected);
					}

					if (m_shiftIndex == -1)
					{
						m_shiftIndex = m_thumbnails.IndexOf(m_selectedThumbnails.Last.Value);
					}
				}
				else if (args.Button == MouseButtons.Right)
				{
					if ((ModifierKeys & Keys.Control) == 0 && !m_selectedThumbnails.Contains(selected))
					{
						m_selectedThumbnails.Clear();
						m_selectedThumbnails.AddLast(selected);
						selectionChanged = true;
						m_shiftIndex = m_thumbnails.IndexOf(m_selectedThumbnails.Last.Value);
					}
				}

				ForceUpdate();

				if (args.Button == MouseButtons.Right)
				{
					BuildThumbnailContextMenu();
					m_contextMenu.Show(m_canvas, new Point(args.X, args.Y));
				}

				if (selectionChanged)
				{
					SelectionChanged.Invoke(m_selectedThumbnails);
				}
			}
		}

		private void Handler_MouseDoubleClick(object sender, MouseEventArgs args)
		{
			Handler_MenuItem_Open(sender, args);
		}

		private void Handler_MenuItem_Open(object sender, EventArgs args)
		{
			foreach (Thumbnail thumbnail in m_selectedThumbnails)
			{
				try
				{
					Process.Start(thumbnail.FilePath);
				}
				catch (Win32Exception e)
				{
					if (e.NativeErrorCode == 1155)
					{
						MessageBox.Show(String.Format("Could not open file \"{0}\"", thumbnail.FilePath));
					}
					else
					{
						throw;
					}
				}
			}
		}

		private class PaintBox : PictureBox
		{
			protected override void OnPaint(PaintEventArgs pe)
			{
				ThumbnailViewPanel parent = (ThumbnailViewPanel)Parent;

				for (int row = parent.m_startRow, drawnCount = 0; drawnCount <= parent.m_visibleRowCount; row++, drawnCount++)
				{
					for (int col = 0; col < parent.m_columnCount; col++)
					{
						// if we've already rendered everything, stop iterating
						if ((row * parent.m_columnCount + col >= parent.m_thumbnails.Count))
						{
							return;
						}

						int left = col * parent.m_thumbSize;
						int top = drawnCount * parent.m_thumbSize;
						Point origin = new Point(left, top);
						int index = row * parent.m_columnCount + col;

						// highlight if selected
						if (parent.m_selectedThumbnails.Contains(parent.m_thumbnails[index]))
						{
							pe.Graphics.FillRectangle(Brushes.Aqua, origin.X, origin.Y, parent.m_thumbSize, parent.m_thumbSize);
						}

						pe.Graphics.DrawImage(parent.m_thumbnails[index].Image, origin);
					}
				}
			}
		}
	}
}
